/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Multicast service core functionality implementation file.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
//*****************************************************************************
//*****************************************************************************
#include "multicastcore.h"
#include "multicasttable.h"
#include "apservicebase.h"
#include "owndebug.h"
#include "ownutils.h"
#include "idmap.h"
#include <inttypes.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>

//*****************************************************************************
//*****************************************************************************
typedef struct SMulticastCore
{
  uint32_t local_nodeid;
} TMulticastCore;
//*****************************************************************************
//*****************************************************************************
static TMulticastCore multicastcore;
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

inline static int isValidOwnerAccess(TMulticastTableEntry *entry, 
                                     const TAPMulticastCtrl *mcctrl, 
                                     const TFastProtocolHeader *fphdr)
{
  return (entry->owner_key == mcctrl->owner_key);
}
//*****************************************************************************

inline static int isValidSubscriberAccess(TMulticastTableEntry *entry, 
                                          const TAPMulticastCtrl *mcctrl, 
                                          const TFastProtocolHeader *fphdr)
{
  return (entry->access_key == mcctrl->access_key &&
          entry->type != KMulticastTypeClosed);
}
//*****************************************************************************

inline static int isValidMulticastAccess(TMulticastTableEntry *entry, 
                                         const TAPMulticastPayload *mcpayload, 
                                         const TFastProtocolHeader *fphdr)
{
  int result = 0;

  if (entry->type == KMulticastTypePublic && 
      entry->access_key == mcpayload->access_key) {
    // Entry is public and key matches. --> OK
    result = 1;

  } else if (entry->type == KMulticastTypePrivate &&
             entry->access_key == mcpayload->access_key &&
             entry->owner_id == fphdr->source_id) {
    // Entry is private, but key matches and owner_id matches source_id --> OK.
    result = 1;
    
  }

  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

void multicastcoreInit(int maxIds, int maxSubs)
{
  memset(&multicastcore, 0x00, sizeof(TMulticastCore));
  multicasttableInit(maxIds, maxSubs);
}
//*****************************************************************************

void multicastcoreDeinit(void)
{
  multicasttableDeinit();
}
//*****************************************************************************

int multicastcoreRegisterMulticastId(const uint32_t mcast_id, 
                                     const uint64_t mcast_owner_key, 
                                     const uint64_t mcast_access_key,
                                     const EMulticastType type)
{
  int result = -1;
  TMulticastTableEntry *entryptr = NULL;
  result = multicasttableCreateEntry(mcast_id, &entryptr);
  if (result == 0) {
    // Created successfully, now the entryptr contains valid data.
    // Next, save the owner id && given owner_key.
    entryptr->owner_id = 0x00000000;
    entryptr->owner_key = mcast_owner_key;
    entryptr->access_key = mcast_access_key;
    entryptr->type = type;
    
    dPrint(ODTEST,"Succesfully registered MulticastId 0x%08lx",
           mcast_id);
    
    result = 0;

  } else {
    dPrint(ODTEST,"Failed to reserve MulticastID %08x - already taken!", mcast_id);
    
  }

  return result;
}
//*****************************************************************************

int multicastcoreRequestCreateMulticastId(TAPMulticastFrame *replyframe,
                                          TAPMulticastCtrl *replyctrl,
                                          const TAPMulticastFrame *mcframe,
                                          const TAPMulticastCtrl *mcctrl,
                                          const TFastProtocolHeader *fphdr)
{
  int result = -1;

  // Require both headers
  if (mcframe && mcctrl) {
    
    // Direct copy of input frame + reply flag
    *replyframe = *mcframe;
    replyframe->flags = KAPMulticastFlagReply;
    
    // Now, make a call to multicasttable and get an unique id. (This
    // doesn't reserve anything, so the id must be reserved to be valid)
    const uint32_t newId = multicasttableGetUniqueMulticastId();
    
    // Reserve the ID by creating a new empty multicast entity.
    TMulticastTableEntry *entryptr = NULL;
    result = multicasttableCreateEntry(newId, &entryptr);
    if (result == 0) {
      // Created successfully, now the entryptr contains valid data.
      // Next, save the owner id && given owner_key.
      entryptr->owner_id = fphdr->source_id;
      entryptr->owner_key = mcctrl->owner_key;
      
      // Finally, assign results to caller
      replyctrl->result = KAPMulticastResultOK;
      replyctrl->multicast_id = newId;
      replyctrl->owner_key = entryptr->owner_key;
      replyctrl->access_key = 0x00;

      dPrint(ODTEST,"Succesfully created new multicastId 0x%08lx for client 0x%08lx",
             replyctrl->multicast_id, fphdr->source_id);

    } else {
      replyctrl->result = KAPMulticastResultFailedUnknown;
      
    }
    
  } else {
    replyctrl->result = KAPMulticastResultFailedUnknown;
    
  }


  // -1 on error, 0 on success
  return result;
}
//*****************************************************************************

int multicastcoreRequestPublishPublic(TAPMulticastFrame *replyframe,
                                      TAPMulticastCtrl *replyctrl,
                                      const TAPMulticastFrame *mcframe,
                                      const TAPMulticastCtrl *mcctrl,
                                      const TFastProtocolHeader *fphdr)
{
  int result = -1;

  // Require both headers
  if (mcframe && mcctrl) {
    
    // Direct copy of input frame + reply flag
    *replyframe = *mcframe;
    replyframe->flags = KAPMulticastFlagReply;

    // Construct MC frame, store already known information.
    replyctrl->multicast_id = mcctrl->multicast_id;
    replyctrl->owner_key = 0x00;
    

    // Now, find an entry with given id.
    TMulticastTableEntry *entry = multicasttableFindEntry(mcctrl->multicast_id);
    if (entry) {
      // OK. Got entry, check access.
      if (isValidOwnerAccess(entry, mcctrl, fphdr)) {
        // Access match -> Control allowed.
        entry->access_key = mcctrl->access_key;
        entry->type = KMulticastTypePublic;

        // Finally, assign result
        replyctrl->result = KAPMulticastResultOK;

        // Status
        dPrint(ODTEST,"Succesfully Published multicastId 0x%08lx as public by client 0x%08lx",
               replyctrl->multicast_id, fphdr->source_id);
        
        result = 0;
      } else {
        replyctrl->result = KAPMulticastResultAccessDenied;
      
      }

    } else {
      replyctrl->result = KAPMulticastResultIdNotFound;
      
    }
  } else {
    replyctrl->result = KAPMulticastResultFailedUnknown;

  }


  // -1 on error, 0 on success
  return result;
}
//*****************************************************************************

int multicastcoreRequestSubscribe(TAPMulticastFrame *replyframe,
                                  TAPMulticastCtrl *replyctrl,
                                  const TAPMulticastFrame *mcframe,
                                  const TAPMulticastCtrl *mcctrl,
                                  const TFastProtocolHeader *fphdr)
{
  int result = -1;
  
  // Require both headers
  if (mcframe && mcctrl) {
    
    // Direct copy of input frame + reply flag
    *replyframe = *mcframe;
    replyframe->flags = KAPMulticastFlagReply;

    // Construct MC frame.
    replyctrl->multicast_id = mcctrl->multicast_id;
    replyctrl->owner_key = 0x00;
    
    // Now, find an entry with given id.
    TMulticastTableEntry *entry = multicasttableFindEntry(mcctrl->multicast_id);
    if (entry) {
      // OK. Got entry, check access.
      if (isValidSubscriberAccess(entry, mcctrl, fphdr)) {
        
        result = multicasttableEntryAddSubscriber(entry, 
                                                  fphdr->source_id);
        if (result == 0) {
          // Entry succesfully added
          // Now what? Nothing?

          // Finally, assign result
          replyctrl->result = KAPMulticastResultOK;
          
        } else {
          // Entry already existed? Well, this is a success too. I guess :)
          TMulticastSubscriberEntry *sentry = 
            (TMulticastSubscriberEntry *)multicasttableEntryFindSubscriber(entry, 
                                                                           fphdr->source_id);
          assert(sentry != NULL);
          
          // Update timestamp (FIXME: Create own function if required more often)
          time(&sentry->last_update_s);
          
          // Finally, assign result
          replyctrl->result = KAPMulticastResultOK;
        }
        
        
      } else {
        replyctrl->result = KAPMulticastResultAccessDenied;
        
      }

    } else {
      replyctrl->result = KAPMulticastResultIdNotFound;
      
    }
  } else {
    replyctrl->result = KAPMulticastResultFailedUnknown;

  }


  // -1 on error, 0 on success
  return result;
}
//*****************************************************************************

int multicastcoreRequestUnsubscribe(TAPMulticastFrame *replyframe,
                                    TAPMulticastCtrl *replyctrl,
                                    const TAPMulticastFrame *mcframe,
                                    const TAPMulticastCtrl *mcctrl,
                                    const TFastProtocolHeader *fphdr)
{
  int result = -1;
  
  // Require both headers
  if (mcframe && mcctrl) {
    
    // Direct copy of input frame + reply flag
    *replyframe = *mcframe;
    replyframe->flags = KAPMulticastFlagReply;

    // Construct MC frame.
    replyctrl->multicast_id = mcctrl->multicast_id;
    replyctrl->owner_key = 0x00;
    
    // Now, find an entry with given id.
    TMulticastTableEntry *entry = multicasttableFindEntry(mcctrl->multicast_id);
    if (entry) {
      // OK. Got entry, check access.
      if (isValidSubscriberAccess(entry, mcctrl, fphdr)) {
        
        result = multicasttableEntryDeleteSubscriber(entry, 
                                                     fphdr->source_id);
        if (result == 0) {
          // Entry succesfully added
          // Now what? Nothing?

          // Finally, assign result
          replyctrl->result = KAPMulticastResultOK;
          result = 0;
          
        } else {
          
          // Finally, assign result
          replyctrl->result = KAPMulticastResultIdNotFound;
        }
        
        
      } else {
        replyctrl->result = KAPMulticastResultAccessDenied;
        
      }
      
    } else {
      replyctrl->result = KAPMulticastResultIdNotFound;
      
    }
  } else {
    replyctrl->result = KAPMulticastResultFailedUnknown;
    
  }
  
  // -1 on error, 0 on success
  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

static int cmphubid(const void *p1, const void *p2)
{
  return *(int*)p1 < *(int*)p2;
}
//*****************************************************************************

static int constructExplicitMulticastFrame(uint8_t *buffer, 
                                           const TMulticastTableEntry *entry,
                                           const uint8_t *payload,
                                           const unsigned int payloadsize,
                                           const uint32_t source_id)
{
  int result = 0;

  TMulticastSubscriberIterator msi;
  TMulticastSubscriberEntry *sentry = NULL;
  TFastProtocolHeader *fphdr = (TFastProtocolHeader *)buffer;
  uint32_t *exp_mcast_addr = (uint32_t *)(buffer+sizeof(TFastProtocolHeader));
  uint8_t *payload_location = NULL;
  const unsigned int subscriber_count = multicasttableEntryGetSubscriberCount(entry);

  // Init FastProtocol header and set up for Explicit multicast operation
  fphdr->protocol_id.protocol_number = FASTPROTOCOL_NUMBER;
  fphdr->protocol_id.total_size = ( sizeof(TFastProtocolHeader) + 
                                    subscriber_count*sizeof(uint32_t) + 
                                    payloadsize );
  fphdr->statusvalue = 0x00;
  fphdr->ttl = FASTPROTOCOL_DEFAULT_TTL;
  fphdr->flags = FASTPROTOCOL_FLAG_MODE_EXPLICIT_MULTICAST; // No error or congestion reports from here
  fphdr->target_count = subscriber_count;
  fphdr->source_id = source_id;


  // Store all subscriber ids.
  multicasttableEntryGetSubscriberIterator(entry, &msi);
  int count = 0;
  while((sentry=multicasttableEntryGetNextMulticastSubscriberEntry(entry,&msi))) {
    *exp_mcast_addr = sentry->subscriber_id;
    exp_mcast_addr++;
    count++;
  }
  assert(count == subscriber_count);

  // Do qsort()
  // FIXME: Instead of sorting on every call, the "exp receiver" table could be cached easily. 
  qsort((void*)(buffer+sizeof(TFastProtocolHeader)), 
        fphdr->target_count, sizeof(uint32_t), cmphubid);
  
  // After all elements processed, comes the payload.
  payload_location = (uint8_t *) exp_mcast_addr;
  memcpy(payload_location, payload, payloadsize);
  

  return result;
}
//*****************************************************************************

int multicastcoreRequestMulticast(TAPMulticastFrame *replyframe,
                                  TAPMulticastCtrl *replyctrl,
                                  TProtocolDisp *link,
                                  const TAPMulticastFrame *mcframe, 
                                  const TAPMulticastCtrl *mcctrl,
                                  const TAPMulticastPayload *mcpayload, 
                                  const TFastProtocolHeader *fphdr)
{
  int result = -1;
  
  // Require both headers
  if (mcframe && mcpayload) {
    
    // Now, find an entry with given id.
    TMulticastTableEntry *entry = multicasttableFindEntry(mcpayload->multicast_id);
    if (entry) {

      // Check that access control is in effect
      if (isValidMulticastAccess(entry, mcpayload, fphdr)) {
        
        // The size of payload in multicast payload frame. (total size - headers)
        const unsigned int multicast_payload_size = ( fphdr->protocol_id.total_size - 
                                                      sizeof(TFastProtocolHeader) - 
                                                      sizeof(TAPMulticastFrame) - 
                                                      sizeof(TAPMulticastPayload) );

        // Number of subscribers in this multicastgroup.
        const unsigned int subs = multicasttableEntryGetSubscriberCount(entry);

        if (subs > 0) {

          // Total required memory space for Multicast frame.
          long unsigned int total_size = ( sizeof(TFastProtocolHeader) +             // For EXP multicast entries
                                           subs*sizeof(uint32_t) +
                                           multicast_payload_size); // Same size applies to new one
          
          uint8_t *multicast_buffer = (uint8_t *)malloc(total_size);
          assert(multicast_buffer != NULL);
          
          // Create pointers to correct elements.
          result = constructExplicitMulticastFrame(multicast_buffer, 
                                                   entry,
                                                   mcpayload->payload,
                                                   multicast_payload_size,
                                                   fphdr->source_id);
          
          // Send!
          result = protocolDispSendPacket(link, (void*)multicast_buffer, total_size);
          
          
          ///          dPrint(ODTEST,"Sent multicast buffer with %u bytes, to %u subscribers", 
          //       total_size, subs);
          
          //  Release buffer
          free(multicast_buffer);


#warning Is this nice?-)
          multicastcorePingNodeTable(link, entry, 1.000);

        } else {
          dPrint(ODTEST,"No subscribers - nothing sent");

        }

        // Now, modify result according to current result ( Callbacks
        // may clear out invalid recipients later)
        if (!result) {
          result = 1;
          
          // At this point, we should check whether the Multicast sender expects to be notified.
          if (mcframe->flags & KAPMulticastFlagWantDeliveryCount) {
            // Direct copy of input frame + flag reassign.
            *replyframe = *mcframe;
            replyframe->flags = KAPMulticastFlagReply|KAPMulticastFlagWantDeliveryCount;
            replyctrl->multicast_id = mcctrl->multicast_id;
            replyctrl->owner_key = 0x0000000000000000;
            replyctrl->access_key = 0x0000000000000000;
            replyctrl->result = subs;
            result = 0;
          }
        }
        
      } else {
        // ACCESS DENIED; FIXME; SHould be delivered?
        //plyctrl->result = KAPMulticastResultAccessDenied;
        
      }
      
    } else {
      // NOT FOUND; FIXME; SHould be delivered?
      //replyctrl->result = KAPMulticastResultIdNotFound;
      
    }
  } else {
    // WTF? FIXME: Should be delivered?
    //replyctrl->result = KAPMulticastResultFailedUnknown;
    
  }


  // -1 on error, 0 on success
  return result;
}
//*****************************************************************************

int multicastcorePingNode(TProtocolDisp *link,
                          const uint32_t node_id)
{
  // Construct Query
  const uint8_t data = 'P';
  const unsigned int testbuffer_size = ( sizeof(TFastProtocolHeader) + 1 );
  uint8_t testbuffer[testbuffer_size];

  TFastProtocolHeader *fpframe = (TFastProtocolHeader*)testbuffer;
  uint8_t *payload = (uint8_t*)(testbuffer+sizeof(TFastProtocolHeader));


  // AUTO-SETUP CALL;; If local_nodeid is UNSET, set it here. (Not the cleanest solution, though)
  if (!multicastcore.local_nodeid) 
    multicastcore.local_nodeid = apservicebaseGetLocalNodeId();

  
  // Setup FastProtocolFrame
  fpframe->source_id = multicastcore.local_nodeid;
  fpframe->target_id = node_id;
  fpframe->flags = FASTPROTOCOL_FLAG_REPORT_ERROR|FASTPROTOCOL_FLAG_REPORT_STATUS;
  fpframe->ttl = FASTPROTOCOL_DEFAULT_TTL;
  fpframe->statusvalue = 0x00;
  fpframe->protocol_id.protocol_number = FASTPROTOCOL_NUMBER;
  fpframe->protocol_id.total_size = testbuffer_size;

  // copy payload
  payload[0] = data;

  // Send!
  return protocolDispSendPacket(link,(void*)testbuffer,testbuffer_size);
}
//*****************************************************************************

int multicastcorePingNodeTable(TProtocolDisp *link,
                               TMulticastTableEntry *entry,
                               float check_probability)
{
  int r = 0; 
  TMulticastSubscriberIterator msi;
  TMulticastSubscriberEntry *sentry = NULL;


  assert(check_probability >= 0.00 && check_probability <= 1.00);
  
  // Store all subscriber ids.
  multicasttableEntryGetSubscriberIterator(entry, &msi);
  while(r == 0 && 
        (sentry = multicasttableEntryGetNextMulticastSubscriberEntry(entry, &msi))) {
    const uint32_t sid = sentry->subscriber_id;
    const float v = ownRandom_get_range(0.00, 1.00);
    if (v <= check_probability) {
      // Probe!
      r = multicastcorePingNode(link, sid);
    }
  }
  return r;
}
//*****************************************************************************

int multicastcoreUnsubscribeNodeId(TMulticastTableEntry *entry,
                                   const uint32_t node_id)
{
  int result;
  if ((result = multicasttableEntryDeleteSubscriber(entry, node_id)) == 0) {
    dPrint(ODTEST,"Removed Node %08x from MulticastGroup %u",
           node_id, entry->multicast_id);
  } else {
    dPrint(ODTEST,"Node %08x not part of MulticastGroup %u",
           node_id, entry->multicast_id);
  }
  
  return result;
}
//*****************************************************************************

int multicastcoreUnsubscribeNodeIdFromAll(const uint32_t node_id)
{
#warning EI näin - Iterate all MulticastId:s - remove all found entries.
  uint32_t i=0;
  for(i=1; i < 10; ++i) {
    TMulticastTableEntry *entry = multicasttableFindEntry(i);
    if (entry) multicastcoreUnsubscribeNodeId(entry, node_id);
  }

  return 0;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

